#include <cstdlib>
#include <cstdio>
#include <cstring>
#include "UsingSVM.h"

char* UsingSVM::readline(FILE *input, char line[]) {
	int len;
	if (line == NULL)
		line = new char[max_line_len];
	if(fgets(line, max_line_len, input) == NULL)
		return NULL;

	while(strrchr(line,'\n') == NULL) {
		max_line_len *= 2;
		line = (char *) realloc(line,max_line_len);
		len = (int) strlen(line);
		if(fgets(line+len,max_line_len-len,input) == NULL) break;
	}
	return line;
}

svm_problem& UsingSVM::read_problem(const char filename[]) {
	int n, m;//n denote the data number, m denote the data dim.
	FILE *fp;
	fopen_s(&fp, filename, "r");
	int label, idx;
	double val;
	char *token;

	char *line = readline(fp);
	sscanf_s(line, " %d %d", &n, &m);
	_svmProb.l = n;
	_svmProb.y = new double[n];
	_svmProb.x = new struct svm_node *[n];
	_svmNodePool = new svm_node[n*(m+1)];

	int cnt = 0;
	for (int i=0; i<n; ++i) {
		readline(fp, line);
		_svmProb.x[i] = &_svmNodePool[cnt];
		token = strtok(line, " \t\n");
		sscanf_s(token, " %d", &label);
		_svmProb.y[i] = label;
		for (int j=0; j<m; ++j) {
			token = strtok(NULL, ":");
			if (token == NULL) break;
			if (sscanf_s(token, " %d", &idx) == -1) break;//token may ""
			token = strtok(NULL, " \t\n");
			sscanf_s(token, " %lf", &val);
			_svmNodePool[cnt].index = idx;
			_svmNodePool[cnt].value = val;
			++cnt;
		}
		_svmNodePool[cnt++].index = -1;
	}

	delete[] line;
	fclose(fp);

	_svmM = m;
	_svmN = n;
	return _svmProb;
}

svm_parameter& UsingSVM::initParam(void) {
	// default values
	_svmParam.svm_type = C_SVC;
	_svmParam.kernel_type = RBF;
	_svmParam.degree = 3;
	_svmParam.gamma = 1.0/_svmM;	// 1/num_features
	_svmParam.coef0 = 0;
	_svmParam.nu = 0.5;
	_svmParam.cache_size = 100;
	_svmParam.C = 1;
	_svmParam.eps = 1e-3;
	_svmParam.p = 0.1;
	_svmParam.shrinking = 1;
	_svmParam.probability = 0;
	_svmParam.nr_weight = 0;
	_svmParam.weight_label = NULL;
	_svmParam.weight = NULL;

	return _svmParam;
}

void UsingSVM::init(void) {
	max_line_len = 500;
	_svmNodePool = NULL;
	_svmModel = NULL;
	_svmProb.x = NULL;
	_svmProb.y = NULL;
	_svmParam.weight = NULL;
	_svmParam.weight_label = NULL;
	_svmM = _svmN = -1;
}

void UsingSVM::clear(void) {
	svm_destroy_param(&_svmParam);
	if (_svmModel != NULL)
		svm_free_and_destroy_model(&_svmModel);
	if (_svmProb.y != NULL)
		delete[] _svmProb.y;
	if (_svmProb.x != NULL)
		delete[] _svmProb.x;
	if (_svmNodePool != NULL)
		delete[] _svmNodePool;
	init();
}

void UsingSVM::predict(char modelFile[], char inputName[], char outputName[], bool predict_probability) {
	const svm_model *model = svm_load_model(modelFile);
	int correct = 0;
	int total = 0;
	double error = 0;

	int svm_type=svm_get_svm_type(model);
	int nr_class=svm_get_nr_class(model);
	double *prob_estimates=NULL;
	char *token;
	int n, m;

	struct svm_node *x;

	FILE *output, *input;
	fopen_s(&input, inputName, "r");
	fopen_s(&output, outputName, "w");

	if(svm_check_probability_model(model)==0) {
		fprintf(stderr,"Model does not support probability estimates\n");
		if (predict_probability) return;
	}

	if(predict_probability)
	{
		if (svm_type==NU_SVR || svm_type==EPSILON_SVR)
			printf("Prob. model for test data: target value = predicted value + z,\nz: Laplace distribution e^(-|z|/sigma)/(2sigma),sigma=%g\n",svm_get_svr_probability(model));
		else
		{
			int *labels=new int[nr_class];
			svm_get_labels(model,labels);
			prob_estimates = new double[nr_class];
			fprintf(output,"labels");		
			for(int j=0; j<nr_class; j++)
				fprintf(output," %d",labels[j]);
			fprintf(output,"\n");
			delete[] labels;
		}
	}

	char *line = readline(input);
	sscanf_s(line, " %d %d", &n, &m);
	x = new struct svm_node[m+1];

	for (int i=0; i<n; ++i) {
		double target_label, predict_label;
		int label, idx;
		double val;
		int cnt = 0;

		readline(input, line);
		token = strtok(line, " \t\n");
		sscanf_s(token, " %d", &label);
		target_label = label;

		for (int j=0; j<m; ++j) {
			token = strtok(NULL, ":");
			if (token == NULL) break;
			if (sscanf_s(token, " %d:", &idx) == -1) break;
			x[cnt].index = idx;
			token = strtok(NULL, " \t\n");
			sscanf_s(token, " %lf", &val);
			x[cnt].value = val;
			++cnt;
		}
		x[cnt].index = -1;

		if (predict_probability && (svm_type==C_SVC || svm_type==NU_SVC))
		{
			predict_label = svm_predict_probability(model,x,prob_estimates);
			fprintf(output,"%g",predict_label);
			for(int j=0; j<nr_class; ++j)
				fprintf(output," %g",prob_estimates[j]);
			fprintf(output,"\n");
		}
		else
		{
			predict_label = svm_predict(model, x);
			fprintf(output,"%g\n",predict_label);
		}

		if(predict_label == target_label)
			++correct;
		error += (predict_label-target_label)*(predict_label-target_label);
		++total;
	}
	delete[] x;
	delete[] line;

	if (svm_type==NU_SVR || svm_type==EPSILON_SVR)
	{
		printf("Mean squared error = %g (regression)\n",error/total);
	}
	else
		printf("Accuracy = %g%% (%d/%d) (classification)\n",
		(double)correct/total*100,correct,total);
	if(predict_probability)
		delete[] prob_estimates;
}

svm_model*& UsingSVM::train(void) {
	_svmModel = svm_train(&_svmProb, &_svmParam);
	return _svmModel;
}
